//------------------------------------------------------------------------------
//
// Written Dec-2007 by Trevor Taylor, Software Technology, Australia
//
// Updated to MRDS 2.0 Dec-2008
//
// NOTE: This code does not use all of the latest features of 2.0. See the
// Release Notes that came with RDS 2008 for more information.
// Also, it is not an example of "best practise". It is designed simply to
// illustrate CCR concepts.
//
//------------------------------------------------------------------------------

using Microsoft.Ccr.Core;
using Microsoft.Dss.Core;
using Microsoft.Dss.Core.Attributes;
using Microsoft.Dss.ServiceModel.Dssp;
using Microsoft.Dss.ServiceModel.DsspServiceBase;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml;
using ccrexamples = ProMRDS.Robotics.CCRExamples;

using System.Threading;


namespace ProMRDS.Robotics.CCRExamples
{
    
    /// <summary>
    /// Implementation class for CCRExamples
    /// </summary>
    [DisplayName("CCRExamples")]
    [Description("The CCRExamples Service")]
    // Add the following attribute to the service to create six threads
    // instead of the default, which is probably only two
    //[ActivationSettings(ShareDispatcher=false, ExecutionUnitsPerDispatcher=6)]
    [Contract(Contract.Identifier)]
    public class CCRExamplesService : DsspServiceBase
    {
        /// <summary>
        /// _state
        /// </summary>
        private CCRExamplesState _state = new CCRExamplesState();
        /// <summary>
        /// _main Port
        /// </summary>
        [ServicePort("/ccrexamples", AllowMultipleInstances=false)]
        private CCRExamplesOperations _mainPort = new CCRExamplesOperations();
        /// <summary>
        /// Default Service Constructor
        /// </summary>
        public CCRExamplesService(DsspServiceCreationPort creationPort) : 
                base(creationPort)
        {
        }

        #region Start Method

        /// <summary>
        /// Service Start
        /// </summary>
        protected override void Start()
        {
			base.Start();
			// Add service specific initialization here.

            // Wait until the service has started up completely.
            // This waits 2 seconds (2000 milliseconds)
            Wait(2000);
            Console.WriteLine("Main Thread {0}", Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("Worker Threads: " + Environment.TaskQueue.Dispatcher.WorkerThreadCount);

            // Sample code using Wait()
            // Comment this out if you don't want to wait 10 seconds
            Console.Write("Countdown ...  ");
            for (int i = 10; i > 0; i--)
            {
                Console.Write(i + "  ");
                Wait(1000);
            }
            Console.WriteLine("... Launch!");

            string s;
            bool done = false;
            int num;
            while (!done)
            {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("Select from the following:");
                Console.WriteLine("0 = Exit");
                Console.WriteLine("1 = Spawn");
                Console.WriteLine("2 = Task From Handler");
                Console.WriteLine("3 = Task From Iterator");
                Console.WriteLine("4 = Spawn Iterator");
                Console.WriteLine("5 = Post and Test");
                Console.WriteLine("6 = Receive");
                Console.WriteLine("7 = Choice");
                Console.WriteLine("8 = Join");
                Console.WriteLine("9 = Multiple Item Receiver");
                Console.WriteLine("10 = Multiple Port Receiver");
                Console.WriteLine("11 = Periodic Timer");
                Console.WriteLine("12 = Causality");
                Console.Write("Enter a number: ");
                Console.ResetColor();
                s = Console.ReadLine();
                try
                {
                    // Try to get an integer from the user's input
                    num = int.Parse(s);
                    // Execute the selected example
                    switch (num)
                    {
                        case 0:
                            done = true;
                            break;
                        case 1:
                            Spawn(SimpleHandler);
                            Spawn(SimpleHandler);
                            Spawn(SimpleHandler);
                            Spawn(SimpleHandler);
                            Console.WriteLine("Spawns executed ...");
                            break;
                        case 2:
                            RunFromHandler();
                            break;
                        case 3:
                            RunFromIterator();
                            break;
                        case 4:
                            SpawnIterator<int>(5, SpawnIteratorExample);
                            Console.WriteLine("Spawn Iterator executed ...");
                            break;
                        case 5:
                            PostAndTest();
                            break;
                        case 6:
                            Receive();
                            break;
                        case 7:
                            Choice();
                            break;
                        case 8:
                            Join();
                            break;
                        case 9:
                            MultipleItemReceive();
                            break;
                        case 10:
                            MultiplePortReceive();
                            break;
                        case 11:
                            // Reset the counter and kick off the timer
                            TimerCounter = 0;
                            Activate(Arbiter.Receive(false, TimeoutPort(10), PeriodicTimerExample));
                            break;
                        case 12:
                            Causality();
                            break;
                        default:
                            Console.ForegroundColor = ConsoleColor.Red;
                            Console.WriteLine("Not a valid selection");
                            Console.ResetColor();
                            break;
                    }
                }
                catch
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine("Please enter only numbers");
                    Console.ResetColor();
                }
                // This is so that tasks will not overlap (by much)
                Wait(500);
            }

            // Close down the service
            ControlPanelPort.Shutdown();

        }

        void PressEnter()
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.Write("Press Enter: ");
            Console.ResetColor();
            Console.ReadLine();
        }

        private IEnumerator<ITask> SleepTimer(int delay)
        {
            Console.WriteLine("Wait");
            yield return Arbiter.Receive<DateTime>(false,
                TimeoutPort(delay),
                delegate(DateTime time)
                {
                }
            );
            Console.WriteLine("Wait done");
            yield break;
        }


        void Wait(int millisec)
        {
            Thread.Sleep(millisec);
        }

        /*
        /// <summary>
        /// Wait for a specified period of time
        /// </summary>
        /// <param name="millisec">Delay time in milliseconds</param>
        /// <remarks>Suspends execution without using Thread.Sleep(), but creates a deadlock.</remarks>
        void Wait(int millisec)
        {
            // Create OS event used for signalling.
            // By creating a new one every time, you can call this routine
            // multiple times simultaneously because there is no shared resource.
            AutoResetEvent signal = new AutoResetEvent(false);

            // Schedule a CCR Timeout task that will execute in parallel with
            // this method and signal when the Timeout has completed.
            Activate(
                Arbiter.Receive(
                    false,
                    TimeoutPort(millisec),
                    delegate(DateTime timeout)
                    {
                        // Done. Signal so that execution can continue.
                        signal.Set();
                    }
                )
            );

            // Block until Timeout completes
            signal.WaitOne();
        }
        */

        #endregion

        #region Run From Handler and Spawn

        //------------------------------------------------------------------------------
        // Run a Task from a Handler
        void RunFromHandler()
        {
            // Explicitly create a Dispatcher so we can control the pool size
            Dispatcher d = new Dispatcher(3, "Test Pool");
            DispatcherQueue q = new DispatcherQueue("Test Queue", d);

            // Activate FOUR tasks with a pool of THREE threads
            Arbiter.Activate(q,
                Arbiter.FromHandler(SimpleHandler),
                Arbiter.FromHandler(SimpleHandler),
                Arbiter.FromHandler(SimpleHandler),
                Arbiter.FromHandler(SimpleHandler));
        }

        // Simple Handlers do not take parameters and do not return anything
        void SimpleHandler()
        {
            // Handler does not really do anything
            Console.WriteLine("Start Simple Handler Thread {0}", Thread.CurrentThread.ManagedThreadId);
            for (int i = 0; i < 10; i++)
            {
                Wait(100);
                Console.Write(i + " ");
            }

            // Try adding PressEnter() and see what happens.
            // It gets complicated!
            //PressEnter();

            Console.WriteLine("Finished Simple Handler Thread {0}", Thread.CurrentThread.ManagedThreadId);
        }

        #endregion


        #region Run From Iterator

        //------------------------------------------------------------------------------
        void RunFromIterator()
        {
            Dispatcher d = new Dispatcher(4, "Test Pool");
            DispatcherQueue taskQ = new DispatcherQueue("Test Queue", d);

            Console.WriteLine("Before Iterator submitted - thread {0}", Thread.CurrentThread.ManagedThreadId);

            Arbiter.Activate(taskQ,
                Arbiter.FromIteratorHandler(IteratorExample),
                Arbiter.FromIteratorHandler(IteratorExample));

            Console.WriteLine("After Iterator submitted - thread {0}", Thread.CurrentThread.ManagedThreadId);

        }

        // An iterator for use with Arbiter.FromIterator
        private static IEnumerator<ITask> IteratorExample()
        {
            Port<int> p1 = new Port<int>();
            Port<int> p2 = new Port<int>();

            p1.Post(0);

            bool done = false;

            while (!done)
            {
                yield return Arbiter.Receive(false, p1,
                    delegate(int i)
                    {
                        Console.WriteLine("P1 Thread {0}: {1}", Thread.CurrentThread.ManagedThreadId, i);
                        p2.Post(i + 1);
                    }
                );

                yield return Arbiter.Receive(false, p2,
                    delegate(int i)
                    {
                        Console.WriteLine("P2 Thread {0}: {1}", Thread.CurrentThread.ManagedThreadId, i);
                        if (i >= 10)
                            done = true;
                        else
                            p1.Post(i + 1);
                    }
                );
            }

            yield break;
        }

        #endregion

        #region Spawn Iterator

        //------------------------------------------------------------------------------
        // A slightly different iterator with a parameter for SpawnIterator
        private IEnumerator<ITask> SpawnIteratorExample(int n)
        {
            Port<int> p1 = new Port<int>();
            Port<int> p2 = new Port<int>();

            /*
            // Sample code creating its own timer which is equivalent to:
            // yield return Arbiter.Receive(false, TimeoutPort(5000),
            //     delegate(DateTime dt) { });
            Console.WriteLine("Waiting");
            Dispatcher d = new Dispatcher(4, "Test Pool");
            DispatcherQueue taskQ = new DispatcherQueue("Test Queue", d);
            Port<DateTime> timerPort = new Port<DateTime>();
            // To use the DSS Dispatcher Queue:
            //Environment.TaskQueue.EnqueueTimer(TimeSpan.FromMilliseconds(5000), timerPort);
            taskQ.EnqueueTimer(TimeSpan.FromMilliseconds(5000), timerPort);
            yield return Arbiter.Receive(false, timerPort,
                delegate(DateTime dt) { });
            Console.WriteLine("Wait Finished");
            */

            p1.Post(n);

            bool done = false;

            while (!done)
            {
                yield return Arbiter.Receive(false, p1, delegate(int i)
                {
                    Console.WriteLine("P1 Thread {0}: {1}", Thread.CurrentThread.ManagedThreadId, i);
                    p2.Post(i + 1);
                });

                yield return Arbiter.Receive(false, p2, delegate(int i)
                {
                    Console.WriteLine("P2 Thread {0}: {1}", Thread.CurrentThread.ManagedThreadId, i);
                    if (i >= n + 10)
                        done = true;
                    else
                        p1.Post(i + 1);
                });
            }

            yield break;
        }

        #endregion


        #region Post And Test

        //------------------------------------------------------------------------------
        // Post messages and use Test to retrieve them
        void PostAndTest()
        {
            int i, j;
            // Create a new integer port
            Port<int> intPort = new Port<int>();
            // Post the answer to life the universe and everything
            intPort.Post(42);
            Console.WriteLine("Posted a value");

            // Display the state of the port
            Console.WriteLine(intPort.ToString());

            // Use Test to read from the port (or fail)
            if (intPort.Test(out j))
                Console.WriteLine("Got value: " + j);
            else
                Console.WriteLine("No value!");

            // View the port status again
            Console.WriteLine(intPort.ToString());
            // Try Test a second time with nothing to read
            if (intPort.Test(out j))
                Console.WriteLine("Got value: " + j);
            else
                Console.WriteLine("No value!");
            // Final status
            Console.WriteLine(intPort.ToString());

            // Pause for user to read the output
            PressEnter();
        }

        #endregion


        #region Receive

        //------------------------------------------------------------------------------
        // Receive a message on a port
        void Receive()
        {
            // Create a port
            Port<int> intPort = new Port<int>();

            // Create a Receiver and place it in the Dispatcher Queue
            // This one is NOT persistent so it only fires once
            Activate(
                Arbiter.Receive(false, intPort,
                    delegate(int n)
                        { Console.WriteLine("Receiver 1: " + n.ToString()); }
                )
            );

            // Add another receiver, but make it persistent
            Activate(
                Arbiter.Receive(true, intPort,
                    delegate(int n)
                    { Console.WriteLine("Receiver 2: " + n.ToString()); }
                )
            );

            Wait(100);
            Console.WriteLine("Receivers Activated:\n" + intPort.ToString());

            // Post a message
            // We could do this even before the receivers were
            // activated because it will just stay in the queue.
            // However, we want to see the effect on the receiver
            // queue in the port.
            intPort.Post(10);
            Wait(100);
            Console.WriteLine("After First Post:\n" + intPort.ToString());

            // Wait a while then post another message
            Wait(100);
            intPort.Post(-2);
            // The Receiver is still active so wait for it to process
            // the second message as well
            Wait(100);
            // And finally a third message ...
            intPort.Post(123);
            Wait(100);
        }

        #endregion

        #region Choice

        //------------------------------------------------------------------------------
        // Choice -- Choose the first message to arrive (Logical OR)
        void Choice()
        {
            // Create a PortSet that takes two different data types
            PortSet<bool, int> ps = new PortSet<bool, int>();

            // If you post the messages BEFORE setting up the Choice,
            // you might see a different result compared to posting
            // them AFTER it has been created
            //ps.Post(1000000);
            //ps.Post(true);

            // Create the Choice and activate it
            Activate(
                Arbiter.Choice<bool, int>(ps,
                // Create delegates for each type in the PortSet
                    delegate(bool b)
                    { Console.WriteLine("Choice: " + b.ToString()); },
                    delegate(int n)
                    { Console.WriteLine("Choice: " + n.ToString()); }
                )
            );

            // Post two messages - Only one will be selected
            // NOTE: If you run this often enough, you might see
            // cases where either of these is displayed because the
            // result is indeterminate if there are messages of both
            // types in the port when the Choice executes
            ps.Post(1000000);
            ps.Post(true);
        }

        #endregion


        #region Join

        //------------------------------------------------------------------------------
        // Join -- Wait for two messages (Logical AND)
        void Join()
        {
            // Set up three different ports
            Port<bool> p1 = new Port<bool>();
            Port<int> p2 = new Port<int>();
            Port<string> p3 = new Port<string>();

            // Join on ports p1 and p2
            Arbiter.Activate(Environment.TaskQueue,
                Arbiter.JoinedReceive(
                    false, p1, p2,
                    delegate(bool b, int i)
                    {
                        Console.WriteLine("Join 1: {0} {1}", b, i);
                        // Now post to p2 so the other Join can complete
                        p2.Post(i+1);
                    }
                )
            );

            // Join on ports p2 and p3
            Arbiter.Activate(Environment.TaskQueue,
                Arbiter.JoinedReceive(
                    false, p2, p3,
                    delegate(int i, string s)
                    {
                        Console.WriteLine("Join 2: {0} {1}", i, s);
                        // Now post to p2 so the other Join can complete
                        p2.Post(i-1);
                    }
                )
            );

            // Now post to the ports
            // NOTE: It is not possible to tell which Join will be
            // executed because it depends how quickly the messages
            // arrive. In general, all three messages would not be
            // sent at the "same time" as in the code below.

            p1.Post(true);
            p3.Post("hello");
            p2.Post(99);
        }

        #endregion

        #region Multiple Item Receive

        //------------------------------------------------------------------------------
        // Receive multiple messages of the same type
        void MultipleItemReceive()
        {
            Port<int> p = new Port<int>();

            Arbiter.Activate(Environment.TaskQueue,
                Arbiter.MultipleItemReceive(
                    true, p, 6,
                    delegate(int[] array)
                    {
                        string s = "";
                        for (int i = 0; i < array.Length; i++)
                            s += array[i].ToString() + " ";
                        Console.WriteLine("{0} Items: {1}", array.Length, s);
                    }
                )
            );

            for (int i = 0; i < 4; ++i)
                p.Post(i + 1);
            Wait(100);

            Console.WriteLine(p.ToString());

            for (int i = 0; i < 4; ++i)
                p.Post(i + 1);
            Wait(100);

            Console.WriteLine(p.ToString());

            for (int i = 0; i < 4; ++i)
                p.Post(i + 1);
            Wait(100);

            Console.WriteLine(p.ToString());
        }

        #endregion


        #region Multiple Port Receive

        //------------------------------------------------------------------------------
        // Receive multiple messages of the different types
        void MultiplePortReceive()
        {
            // Create a port set that accepts two different data types
            PortSet<int, double> pSet = new PortSet<int, double>();

            //Arbiter.Activate(Environment.TaskQueue,
            Activate(
                Arbiter.MultipleItemReceive(
                    pSet, 10,
                    delegate(ICollection<int> colInts, ICollection<double> colDoubles)
                    {
                        Console.Write("Ints: ");
                        foreach (int i in colInts)
                            Console.Write(i + " ");
                        Console.WriteLine();
                        Console.Write("Doubles: ");
                        foreach (double d in colDoubles)
                            Console.Write("{0:F2} ", d);
                        Console.WriteLine();
                    }
                )
            );

            // Generate some random numbers and post them using
            // different message types depending on their values
            Random rnd = new Random();
            for (int i = 0; i < 10; ++i)
            {
                double num = rnd.NextDouble();
                if (num < 0.5)
                    pSet.Post((int)(num * 100));
                else
                    pSet.Post(num);
            }
        }

        #endregion


        #region Periodic Timer

        //------------------------------------------------------------------------------
        // Demonstrate a timer that counts to 10 running in the background

        static int TimerCounter = 0;

        // A handler that calls itself periodically
        void PeriodicTimerExample(DateTime dt)
        {
            int timeDelay = 1000;

            TimerCounter++;
            if (TimerCounter > 10)
                return;

            Console.WriteLine("Tick {0} ...", TimerCounter);

            // Wait for some time
            Activate(Arbiter.Receive(
                false,
                TimeoutPort(timeDelay),
                PeriodicTimerExample));
        }

        #endregion


        #region Causality

        //------------------------------------------------------------------------------
        // Example of using a Causality
        //
        // NOTE: You must run this example without the Debugger!
        // Otherwise the Debugger will trap the exception and you
        // will not see the Causality working properly.
        //
        private void Causality()
        {
            using (Dispatcher d = new Dispatcher())
            {
                using (DispatcherQueue taskQ = new DispatcherQueue("Causality Queue", d))
                {
                    Port<Exception> ep = new Port<Exception>();
                    Port<int> p = new Port<int>();

                    // Set up a causality for the current thread
                    Dispatcher.AddCausality(new Causality("Test", ep));

                    Console.WriteLine("Main thread: {0}", Thread.CurrentThread.ManagedThreadId);

                    // Set up a receiver that will generate an exception
                    Arbiter.Activate(taskQ, Arbiter.Receive(false, p, CausalityExample));

                    // Post an item and the causality goes with it
                    p.Post(2);
                    Wait(500);

                    Exception e;
                    while (ep.Test(out e))
                        Console.WriteLine("Exception: " + e.Message);
                }
            }
        }

        // Receiver to execute with causality enabled
        private void CausalityExample(int num)
        {
            Console.WriteLine("CausalityExample thread: {0}", Thread.CurrentThread.ManagedThreadId);

            Port<int> p = new Port<int>();
            // Set up another receiver
            Arbiter.Activate(Environment.TaskQueue,
                Arbiter.Receive(false, p,
                // And now an anonymous delegate
                delegate(int n)
                {
                    Console.WriteLine("Anonymous method thread: {0}", Thread.CurrentThread.ManagedThreadId);
                    // Can you spot the deliberate error?
                    int i = 0; n = n / i;
                }
            ));
            // Post a message to activate the receiver
            p.Post(num);
        }

        #endregion


        #region Service Operation Handlers

        /// <summary>
        /// Get Handler
        /// </summary>
        /// <param name="get"></param>
        /// <returns></returns>
        [ServiceHandler(ServiceHandlerBehavior.Concurrent)]
        public virtual IEnumerator<ITask> GetHandler(Get get)
        {
            get.ResponsePort.Post(_state);
            yield break;
        }

        #endregion
    }
}
